<?php

// Application

class Basic{

    # === envXxx(-) =======================================

    // 定义常量
    static function envConst(){
        if(defined('KEY_NUM10')) return;
        define('IS_CGI',     substr(PHP_SAPI,0,3)=='cgi' ? 1 : 0);
        define('IS_WIN',     strstr(PHP_OS,'WIN') ? 1 : 0);
        define('IS_CLI',     PHP_SAPI=='cli' ? 1 : 0);
        define('KEY_NUM10',  '0123456789');
        define('KEY_CHR26',  'abcdefghijklmnopqrstuvwxyz');
        define('KEY_CHR22',  'abcdefghjkmnpqrstuvwxy'); // -iloz
        define('KEY_NUM16',  KEY_NUM10.'abcdef');
        define('KEY_TAB36',  KEY_NUM10.KEY_CHR26); // 极端情况下用
        define('KEY_TAB32',  KEY_NUM10.KEY_CHR22); // (字形可能与数字012混淆)
        define('KEY_TAB30',  '123456789abcdfghjkmnpqrstuvwxy'); // - 0e + iloz (0字形,e读音易混淆)
        define('KEY_TAB24',  '3456789abcdfghjkpqstuvwxy'); // - 012eilmnorz(25) (去除字形读音易混淆者)
    }

    // 获取客户端软件信息
    static function envUA(){
        $val = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
        $val = str_replace(array("'","\\",'"','>','<'), '', $val);
        return $val;
    }
    
    // 获取客户端IP地址 (xf=::1;ra=192.168.0.1,192.168.0.1;cip=127.0.0.1)
    static function envIP($flag=0){
        $a = array('xf'=>'HTTP_X_FORWARDED_FOR', 'ra'=>'REMOTE_ADDR', 'cip'=>'HTTP_CLIENT_IP');
        $ip = ''; //'r'=>'HTTP_X_REAL_FORWARDED_FOR',
        foreach($a as $k=>$v){
            if(!empty($_SERVER[$v])){ 
                $ip .= ($ip ? ";" : '')."$k=".$_SERVER[$v]; 
            }
        }
        return str_replace(["'","\\",'"'],'',$ip);
    }

    # === isXxx(env) =======================================

    // 是否搜索引擎来访
    static function isRobot($uastr=''){
        global $_cbase; $uacfg = $_cbase['uacfg'];
        $kw_spiders = $uacfg['spname'];
        $uastr || $uastr = self::envUA();
        if(preg_match("/($kw_spiders)/i",$uastr)) return true;
        return false;
    }
    // 是否miniProgram()
    static function isMpro($ver=0){
        $wxpos = strpos(self::envUA(), 'miniProgram');
        return $wxpos;
    } 
    // 是否Weixin()
    static function isWeixin($ver=0){
        $wxpos = strpos(self::envUA(), 'MicroMessenger');
        if($ver){
            preg_match('/.*?(MicroMessenger\/([0-9.]+))\s*/', $uagent, $matches);
            return $wxpos ? $matches[2] : '';
        }else{
            return $wxpos;
        }
    }
    // 是否Wework()
    static function isWework(){
        $wxpos = strpos(self::envUA(), 'wxwork/');
        return $wxpos;
    }
    // 是否Qq()
    static function isQq($ver=0){
        $wxpos = strpos(self::envUA(), 'QQBrowser');
        return $wxpos;
    }
    // 是否Mobile
    static function isMobile($ckey=''){
        //return true;
        if(isset($_SERVER['HTTP_X_WAP_PROFILE'])){
            return true;
        }
        if(isset($_SERVER['HTTP_VIA']) && stristr($_SERVER['HTTP_VIA'],"wap")){
            return true;
        }
        $mbstr = "Android|Windows Phone|webOS|iPhone|iPad|BlackBerry";
        if(preg_match("/$mbstr/i",self::envUA())){
            return true;
        }else{
            return false;
        }
    }
    // 是否ajax请求(jq)
    static function isAjax(){ 
        $sk = 'HTTP_X_REQUESTED_WITH'; 
        return !empty($_SERVER[$sk]) && $_SERVER[$sk]=='XMLHttpRequest';
    }

    // 判断是否 isHttps
    static function isHttps() {
        if(isset($_SERVER['HTTPS']) && ('1' == $_SERVER['HTTPS'] || 'on' == strtolower($_SERVER['HTTPS']))){
            return true; // 1:Apache, on:IIS
        }elseif(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO']==='https'){
            return true; // 2:Nginx 
        }elseif(!empty($_SERVER['HTTP_FRONT_END_HTTPS']) && strtolower($_SERVER['HTTP_FRONT_END_HTTPS'])!=='off') {
            return true; // 3:? 
        }elseif(isset($_SERVER['SERVER_PORT']) && ('443' == $_SERVER['SERVER_PORT'] )) {
            return true;
        }
        return false;
    }
    // 检查内网ip地址
    static function isLocal($ip=''){
        $ip || $ip = $_SERVER['REMOTE_ADDR'];
        if(strpos($ip,'.')){ // IPv4:
            $pa = explode('.',$ip);
            $f1 = in_array($pa[0],array('10','127')); // 10.*, 127.*
            $f2 = $pa[0]==192 && ($pa[1]==168); // 192.168.*
            $f3 = $pa[0]==172 && ($pa[1]>=16 && $pa[1]<=31); // 172.16.* ~ 172.31.*
            return $f1 || $f2 || $f3;
        }elseif(strpos($ip,':')){ // IPv6
            $arr = explode(':',$ip);
            return in_array($arr[0],array('FE80','FEC0'));
        }else{ // ::1=127.0.0.1=localhost
            return in_array($ip,array('::1'));
        }
    }

    # === isXxx(str) =======================================

    static function isKey($str,$m1=3,$m2=12,$type='') {
        $new = self::filKey($str,$type);
        $len = strlen($new);
        return $str===$new && $len>=$m1 && $len<=$m2;
    }
    // type:mob,400,800,tel
    static function isTel($val,$type='mob') {
        if($type=='mob'){ // 138-1234-5678
            return strlen($val) > 10 && preg_match("/^1[\d]{2}[\-]{0,1}[\d]{4}[\-]{0,1}[\d]{4}$/", $val);
        }elseif($type=='400'){ // 400-123-4567
            return strlen($val) > 9 && preg_match("/^400[\-]{0,1}[\d]{3}[\-]{0,1}[\d]{4}$/", $val);
        }elseif($type=='800'){ // 800-123-4567
            return strlen($val) > 9 && preg_match("/^800[\-]{0,1}[\d]{3}[\-]{0,1}[\d]{4}$/", $val);
        }else{ // tel: 010-1234-5678
            return strlen($val) > 9 && preg_match("/^0[\d]{2,3}[\-]{0,1}[\d]{3,4}[\-]{0,1}[\d]{4}$/", $val);
        }
        
    }
    static function isUrl($url) {
        return filter_var($email, FILTER_VALIDATE_URL);
    }
    static function isMail($email) {
        return filter_var($email, FILTER_VALIDATE_EMAIL);
    }
    // 省份证号认证/不全最后一位
    static function isIdCard($idno, $rch=0){
        if(!$rch && strlen($idno)!=18){ return 0; }
        $w = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
        $y = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']; 
        $sum = 0;
        for($i=0; $i<17; $i++){
            $sum += $idno[$i] * $w[$i];
        } 
        $mod = $sum % 11; // 取余
        if($rch){
            return $y[$mod];
        }else{
            return $idno[17] == $y[$mod];
        }
    } //原文链接：https://blog.csdn.net/xfjpeter/article/details/85092576

    // 检查字符串是否是UTF8编码,是返回true,否则返回false
    static function isUtf8($str,$re='T/F'){
        $flag = mb_detect_encoding($str, array('ASCII','GB2312','GBK','BIG5','UTF-8'));
        if($re=='T/F'){
            return $flag === 'UTF-8';
        }else{
            return $flag;
        }
    }
    // 避免重复转utf-8，只转需要转的文档
    static function isConv($str = ''){
        if(!$str) return FALSE;
        if(mb_detect_encoding($str,'UTF-8',TRUE) == 'UTF-8') return FALSE; //本就是UTF8的编码
        return TRUE;    
    }

    # === char(str) =======================================

    // 计算字符串字节数，英文算一个字节,不管[GBK/utf-8]编码中文算两个字节
    static function chrCount($str){
        return strlen(preg_replace('/([x4e00-x9fa5])/u','**',$str));
    }

    // 截取字符串，英文算一个,中文算一个 
    static function cutCount($str, $len=255){ 
        if(!$str) return ""; 
        return mb_substr($str, $len);
    }
    
    // 截取字符串，得到等宽字符串
    static function cutWidth($string, $length=24, $etc='...') {
        if(!$string) return ""; 
        $clen = 3; // utf8-中文宽度:3字符
        $olen = strlen($string); $mblen=mb_strlen($string); $len2 = $length*2; //中文宽计算
        $pos = 0; $width = 0; $mbcnt = 0;
        if($mblen > $length) {
            //将$length换算成实际UTF8格式编码下字符串的长度
            for($i=0; $i<$olen; $i++) {
                if ($pos >= strlen($string)) { break; }
                if ($width >= $len2) { break; }
                //当检测到一个中文字符时, //大概按一个汉字宽度相当于两个英文字符的宽度
                if(ord($string[$pos]) > 127) { $pos += $clen; $width += 2; }
                else { $pos++; $width++; }
                $mbcnt++;
            }
            $res = $mblen>$mbcnt ? mb_substr($string,0,$mbcnt-1).$etc : $string;
            return $res;
        }else return $string;
    }
    // 格式化数字,$type='Byte'显示占用空间
    static function strByte($num, $type=0){
        $b = 0; $bfix = ''; // Byte
        if(is_numeric($num)){ $b = $num; }
        if($type==='Byte'){
            // TB,PB,EB,ZB,YB,NB,DB
            if($b>pow(1024,4)){
                $b = number_format($b/(pow(1024,4)),2)." (TB) ";
            }else if($b>pow(1024,3)) {
                $b = number_format($b/(pow(1024,3)),2)." (GB) ";
            }else if($b>pow(1024,2)) {
                $b = number_format($b/(pow(1024,2)),2)." (MB) ";
            }else if($b>pow(1024,1)) {
                $b = number_format($b/(pow(1024,1)),2)." (KB) ";
            }else{
                $b = $b." (B) ";
            }
            return $b;
        }else{
            return number_format($b,$type);
        }
    }

    // 隐藏电话,手机,邮件,qq,ip的中间一部分
    // ('13712345678','',3) -> 137****5678
    static function strMask($str, $dstart=0){
        if(strpos($str,'@')>0){
            $a = explode('@',$str);
            $suf = '@*'.substr($a[1],strrpos($a[1],'.'));
            $str = $a[0];
        }else{
            $suf = '';
        }
        $len = strlen($str); 
        if($len<5){
            $res = $str;
        }elseif($len<=8){ // 12***78
            $res = substr($str,0,2).str_repeat('*',strlen($str)-4).substr($str,-2);
        }else{ // 123****5678
            $res = substr($str,0,3).str_repeat('*',strlen($str)-7).substr($str,-4);
        }
        return $res.$suf;
    }

    # === filter(str) =======================================

    // 文本文件
    static function filText($str,$cbr=1){
        if(is_array($str)) {
            foreach($str as $key => $val) $str[$key] = self::filText($val,$cbr);
        }else{// 不用:html(specialchars/entities) : 1-不好记, 2-不兼容数组, 3-php5.4+gbk返回空
            $str = str_replace(array('&','"',"'",'<','>'), array('&amp;','&quot;',"&#039;",'&lt;','&gt;'), $str);
            if($cbr) $str = nl2br($str); // htmlentities($str,3,"UTF-8");
        }
        return $str;
    }
    // 过滤危险的HTML
    static function filHtml($val){
        $string = $val;
        $searcharr = array("/(javascript|jscript|js|vbscript|vbs|about):/i","/on(mouse|exit|error|click|dblclick|key|load|unload|change|move|submit|reset|cut|copy|select|start|stop)/i","/<script([^>]*)>/i","/<iframe([^>]*)>/i","/<frame([^>]*)>/i","/<link([^>]*)>/i","/@import/i");
        $replacearr = array("\\1\n:","on\n\\1","&lt;script\\1&gt;","&lt;iframe\\1&gt;","&lt;frame\\1&gt;","&lt;link\\1&gt;","@\nimport");
        $string = preg_replace($searcharr,$replacearr,$string);
        $string = str_replace("&#","&\n#",$string);
        return $string;
    }
    // 从Html内容中截取字符串，
    static function filHText($xStr,$xLen=0) {
        $xStr = preg_replace("/<\!--.*?-->/si","",$xStr); //注释
        $xStr = strip_tags($xStr); 
        //$xStr = htmlspecialchars_decode($xStr); // 不含<>字符
        $xStr = self::filTrim($xStr); 
        if($xLen) $xStr = self::cutWidth($xStr,$xLen);
        return $xStr; // nl2br($s);
    }
    // 从Html内容过滤样式，适合wap显示
    static function filHWap($str){
        $str = preg_replace('/<img.*?src=["\']?([^"\'\s]*)["\']?[^>]*>/is',"<img src=\"$1\" />",$str);//图片格式化
        $str = preg_replace('/<p[^>]*>\s*<img[^>]*src=["\']?([^"\'\s]*)["\']?[^>]*>/is',"<p style=\"text-align:center;\" ><img src=\"$1\" />",$str);//图片居中
        // text
        $str = preg_replace('/>(\s|&nbsp;|　)*/is',">",$str);//去除空格
        $str = preg_replace('/text-indent:[^;\'"]*;?/i','',$str);
        // iframe
        $str = preg_replace('/width=(\d+)&amp;height=(\d+\.{0,9}\d*)/is',"",$str);
        $str = preg_replace('/<iframe height="\d+" width="\d+"/','<iframe height="250" width="100%"',$str);
        return $str;
        #$val = preg_replace('/\[#.*?#\]/','',safestr($val)); // 安全,分页
    }
    // Form中字符编码
    static function filForm($str) {
        return self::filText($str,0);
    }
    // Account过滤账号
    static function filKey($str,$ext='_'){
        $url = array(
            'url' => FILTER_SANITIZE_URL,
            'email' => FILTER_SANITIZE_EMAIL,
        );
        if(isset($url[$ext])){
            return filter_var($str, $url[$ext]);
        }
        $re = '';
        if(strlen($str)){ //$str=0
            $tmp = KEY_NUM10.KEY_CHR26.$ext; //-._@
            $tab = strtoupper($tmp).strtolower($tmp); 
            for($i=0;$i<strlen($str);$i++) { 
                if(strstr($tab,substr($str,$i,1))) $re .= substr($str,$i,1);
            }
        } 
        return $re;
    }

    // Title过滤标题
    static function filTitle($xStr, $exa=array('\r','\n')){
        $def = array('<','>','"',"'","\\");
        if($exa=='file') $exa = array('/',':','*','?','|'); // \/:*?"<>|
        if(!empty($exa)){
            $def = array_merge($def, $exa);
        }
        $xStr = str_replace($def,'',$xStr); 
        return $xStr;
    }
    // Safe4过滤表单
    static function filSafe4($xStr, $exa=array('%')){
        return self::filTitle($xStr, $exa);
    }
    // 过滤空行和注释
    static function filNotes($data, $sin=1){
        $data = str_replace(array("\r\n", "\r"), "\n", $data);
        $data = preg_replace('/\/\*(.*?)\*\//s','',$data); // /*note*/ 多行注释
        $data = preg_replace("/<\!--.*?-->/s","",$data);   // <!--note--> 多行注释
        $data = preg_replace("/(#[^\n]+)/s","",$data);  // #note 多行注释
        $data = preg_replace("/([\s]{2,})/","\\1",$data);
        $data = preg_replace("/\n+/", "\n", $data);
        if($sin){
            $data = preg_replace('/\/\/(.*?)\ /s','',$data); // 单行注释
            $data = preg_replace('( [\s| ]* )'," ",$data); // 连续空白
        }
        return $data;
    }
    // 多行空白
    static function filL3bl($data, $sin=1){
        $data = preg_replace('/\n[ ]+\n/s',"\n\n",$data); 
        $data = preg_replace('/\n[ ]+\n/s',"\n\n",$data); 
        $data = preg_replace('/\n[ ]+\n/s',"\n\n",$data); 
        $data = preg_replace('/\n\n/s',"\n",$data); 
        $data = preg_replace('/\n\n/s',"\n",$data); 
        $data = preg_replace('/\n\n/s',"\n",$data); 
        return $data;
    }
    // 过滤 python '''/""" 多行注释
    static function filN3py($data, $sin=1){
        $data = preg_replace('/\'\'\'(.*?)\'\'\'/s','',$data); 
        $data = preg_replace('/\"\"\"(.*?)\"\"\"/s','',$data); 
        return $data;
    }

    // 去除多余的空格和换行符
    static function filTrim($str,$mode=0){
        $str = trim($str);
        if($mode==1){ 
            // 去除多个空白
            $str = preg_replace("/[\s]{2,}/", '', $str);
        }elseif($mode==2){ 
            // 去除所有的空格和换行符
            $str = preg_replace("/\s+/", '', $str);
        }else{
            // 去除多余的空格和换行符，只保留(第)一个
            $str = preg_replace("/\s(?=\s)/","\\1",$str); 
        }
        return $str;
    }

    # === kid(str) =======================================

    // 格式化xBase进制
    static function fmtBase32($kTab, $xNum, $xBase, $xLen=6, $xDir='Right'){
        $sKey = $kTab=='' ? KEY_TAB32 : $kTab; $s0 = "";
        if($xBase==0){$xBase=16;}
        $n = $xNum; $m0 = pow($xBase,$xLen);
        if($n>$m0) {
            if($xDir=="Right") {
                $m1 = substr($n,strlen($m0)-$xLen,$xLen);  
                if($m1>$m0){ $m1 = substr($m1,1,strlen($m1)-1); }
            }else{
                $m1 = substr($n,0,$xLen);   
                if($m1>$m0){ $m1 = substr($m1,0,strlen($m1)-1); }
            }
            $n = $m1;
        }
        for($i=$xLen-1; $i>=0; $i--) {
            if($i>0){
                $ni = intval($n/(pow($xBase,$i)));
            }else{
                $ni = $n;
            }
            $si = substr($sKey,$ni,1);
            $s0 = $s0.$si; 
            $n = $n % pow($xBase,$i);
        } 
        return $s0;
    }

    // 随机源table
    static function kidTable($Type='24'){
        #$sSpe = '!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~'; //32个 
        $sSp1 = '!()+,-.;@^_`~'; // 安全13个; '*+-./@_'; // 7个js-escape安全escape; '+-.@_'; // 5个绝对安全
        $sfula = KEY_NUM10.KEY_CHR26.strtoupper(KEY_CHR26);
        switch ($Type){ 
            case "0" : $sOrg = KEY_NUM10; break; 
            case "a" : $sOrg = KEY_CHR26; break;
            case "h" : $sOrg = KEY_NUM16; break; 
            case "k" : $sOrg = KEY_TAB32; break;
            case "A" : $sOrg = strtoupper(KEY_CHR26); break; 
            case "H" : $sOrg = strtoupper(KEY_NUM16); break; 
            case "K" : $sOrg = strtoupper(KEY_TAB32); break;
            case "30": $sOrg = strtoupper(KEY_TAB30); break;
            case "24": $sOrg = strtoupper(KEY_TAB24); break; 
            case "22": $sOrg = strtoupper(KEY_CHR22); break;
            case "52" : $sOrg = $sfula; break; 
            case "sp1" : $sOrg = $sSp1; break; 
            case "fs1" : $sOrg = $sfula.$sSp1; break; 
            default  : $sOrg = strtoupper(KEY_TAB32); break; 
        }
        return $sOrg;
    }

    // 随机码
    static function kidRand($type='24', $len=16){
        $tab = str_shuffle(self::kidTable($type));
        return substr($tab, 0, $len);
    }
    
    // 摸版ID xFmt = md2,md3,mdh,5.5,0
    static function kidTemp($xFmt='md2', $xTime=''){
        $ktab32 = str_replace('e','',KEY_TAB32).'z';
        $sTmp = strtolower($xFmt); 
        if(empty($xTime)){
            $sDate = date("Ymd");
            $sTime = date("His"); 
        }else{
            $xTime = is_numeric($xTime) ? $xTime : strtotime($xTime);
            $sDate = date("Ymd",$xTime); 
            $sTime = date("His",$xTime);
        } 
        $y4 = date("Y",strtotime($sDate)); 
        $md2 = substr($ktab32,substr($sDate,4,2),1).substr($ktab32,substr($sDate,6,2),1); 
        $md3 = substr($ktab32,substr($sDate,4,2),1).substr($sDate,6,2); 
        $h   = substr($ktab32,substr($sTime,0,2),1); 
        $ms = 60 * substr($sTime,2,2) + substr($sTime,4,2);
        $s3 = substr(microtime(),2,3);
        $ms = self::fmtBase32('', intval($ms/4), 32, 2, '');
        $s2 = self::fmtBase32('', $s3, 32, 2, '');
        if(!$xFmt){ // 0
            return array($y4,$md2,$md3,$h,$ms,$s2);
        }elseif($xFmt=='md3'){ // md3
            return "$y4-$md3-$h$ms".substr($s2,0,1);
        }elseif($xFmt=='md3h'){ // md4
            return "$y4-$md3$h-$ms$s2";
        }elseif($xFmt=='mdh'){ // mdh
            return "$y4-$md2$h-$ms$s2";
        }elseif($xFmt=='5.5'){ // 5.5(18613-pdy05)
            return date('y')."$md3-$h$ms$s2";
        } // md2
        return "$y4-$md2-$h$ms".substr($s2,0,1);
    } 

    // 1y1-xhc3w (2017-1229-...)
    static function kidY3x5(){
        $y3 = self::fmtBase32('',date('Y'),32,3);
        $t5 = ((date('m')-1)*31+date('d'))*86400+date('H')*3600+date('i')*60+date('s');
        $t5 = strtotime(date('Y-m-d H:i:s')) - strtotime(date('Y').'-01-01');
        $x5 = self::fmtBase32('',$t5,32,5); 
        return "$y3-$x5";
    }

    # === xxx(str) =======================================

    // 把字符串转化为数组，支持中文
    static function strArr($str, $cset='utf-8'){
        $len = mb_strlen($str, $cset);
        $arr = array();
        for($i=0; $i<$len; $i++){
            $arr[] = mb_substr($str, $i, 1, $cset);
        }   
        return $arr;
    }
}
